package com.upb.webserver.rest;

import cn.hutool.core.collection.CollectionUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.upb.webserver.common.constant.Constant;
import com.upb.webserver.common.exception.WebServerException;
import com.upb.webserver.common.pojo.base.BaseResponse;
import com.upb.webserver.common.pojo.order.ReqAddNewNode2ChainVO;
import com.upb.webserver.common.pojo.order.RespAddNodeVO;
import com.upb.webserver.common.pojo.overview.RespSysOverview;
import com.upb.webserver.common.properties.RestProperties;
import com.upb.webserver.common.returncode.ConstantCode;
import com.upb.webserver.common.tools.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Log4j2
@Service
public abstract class AbstractRestService implements RestService {
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RestProperties properties;

    private static final Map<String, List<RestAddressStatusInfo>> SERVER_ADDRESS_MAP = new ConcurrentHashMap<>();

    /**
     * @param serverName
     * @param addressList
     */
    public synchronized void initServerAddressMap(String serverName, List<String> addressList) {
        if (CollectionUtil.isEmpty(addressList)) {
            log.error("fail exec method[initServerAddressMap]. addressList is null");
            throw new WebServerException(ConstantCode.NOT_FOUND_ADDRESS_CONFIG);
        }

        List<RestAddressStatusInfo> list = new ArrayList<>();
        addressList.stream().forEach(address -> {
            list.add(new RestAddressStatusInfo(address));
        });

        SERVER_ADDRESS_MAP.put(serverName, list);
    }


    /**
     * @param serverName
     * @return
     */
    public RestAddressStatusInfo getNextAddress(String serverName) {
        List<RestAddressStatusInfo> list = SERVER_ADDRESS_MAP.get(serverName);
        if (CollectionUtil.isEmpty(list)) {
            log.error("fail exec method[getNextAddress]. not found config of:{}", serverName);
            throw new WebServerException(ConstantCode.NOT_FOUND_ADDRESS_CONFIG);
        }

        log.debug("serverName:{} ,all address list is:{}", serverName, JsonUtils.objToString(list));
        List<RestAddressStatusInfo> sortedAddressList = list.stream()
                .filter(addressInfo -> Objects.isNull(addressInfo.getLastFailTime()) || (Timestamp.valueOf(LocalDateTime.now()).getTime() - addressInfo.getLastFailTime()) > properties.getSleepTimeAfterServerConnectFail())
                .sorted()
                .collect(Collectors.toList());
        log.debug("serverName:{} ,available address list is:{}", serverName, JsonUtils.objToString(sortedAddressList));

        RestAddressStatusInfo resultAddressInfo = null;
        if (CollectionUtil.isNotEmpty(sortedAddressList)) {
            resultAddressInfo = sortedAddressList.get(0);
        }
        log.debug("finish exec method[getNextAddress] resultAddressInfo:{}", resultAddressInfo);
        return resultAddressInfo;

    }


    /**
     * @param serverName TODO最后一次请求后应该未能返回错误信息
     * @param urlFormat
     * @param uri
     * @param method
     * @param param
     * @param clazz
     * @param <T>
     * @return
     */
    protected <T> T restTemplateExchange(String serverName, String urlFormat, String uri, HttpMethod method, Object param, Class<T> clazz) {
        RestAddressStatusInfo serverAddressStatusInfo;

        WebServerException restException = new WebServerException(ConstantCode.REMOTE_SERVER_REQUEST_FAIL);
        while (true) {

            serverAddressStatusInfo = getNextAddress(serverName);
            if (serverAddressStatusInfo == null) {
                break;
            }

            String url = String.format(urlFormat, serverAddressStatusInfo.address, uri).replaceAll(" ", "").replace("\"", "");
            log.info("url:{}", url);
            log.info("param:{}", JsonUtils.objToString(param));
            try {
                HttpEntity entity = buildHttpEntity(param);// build entity
                if (null == restTemplate) {
                    log.error("fail exec method [restTemplateExchange]. restTemplate is null");
                    throw new WebServerException(ConstantCode.SYSTEM_EXCEPTION);
                }
                ResponseEntity<T> response = restTemplate.exchange(url, method, entity, clazz);
                log.info("response:{}", JsonUtils.objToString(response));

                serverAddressStatusInfo.setLastFailTime(null);
                return response.getBody();
            } catch (ResourceAccessException e) {
                log.error("fail exec method [restTemplateExchange]. ResourceAccessException.", e);
                serverAddressStatusInfo.setLastFailTime(Timestamp.valueOf(LocalDateTime.now()).getTime());
                restException = new WebServerException(ConstantCode.REMOTE_SERVER_REQUEST_FAIL.msg("not found valid remote server"));
                continue;
            } catch (HttpStatusCodeException e) {
                JsonNode error = JsonUtils.stringToJsonNode(e.getResponseBodyAsString());
                log.error("fail exec method [restTemplateExchange]. error:{}", e.getResponseBodyAsString());
                if (ObjectUtils.isEmpty(error.get("message"))) {
                    throw new WebServerException(ConstantCode.REMOTE_SERVER_REQUEST_FAIL);
                }
                String message = error.get("message").asText();
                if (message.contains("code")) {
                    JsonNode errorInside = error.get("message").get("error");
                    throw new WebServerException(errorInside.get("code").asInt(),
                            errorInside.get("message").asText());
                }
                throw new WebServerException(error.get("code").asInt(), message);
            }
        }

        throw restException;
    }


    /**
     *   protected <T> T restTemplateExchange(List<String> addressList, String urlFormat, String uri, HttpMethod method, Object param, Class<T> clazz) {
     *         String serverName = "webaseChainManager";
     *         String serverAddress = getNextAddress(serverName);
     *         if (Objects.isNull(addressList) || addressList.size() == 0) {
     *             log.error("fail exec method [restTemplateExchange]. addressList is null");
     *             throw new WebServerException(ConstantCode.NOT_FOUND_REMOTE_SERVER);
     *         }
     *
     *         ArrayList<String> addressArrayList = new ArrayList<>(addressList);
     *         while (addressArrayList != null && addressArrayList.size() > 0) {
     *             String url = buildRemoteUrl(addressArrayList, urlFormat, uri);//build url
     *             log.info("url:{}", url);
     *             log.info("param:{}", Objects.isNull(param) ? "null" : JsonUtils.objToString(param));
     *             try {
     *                 HttpEntity entity = buildHttpEntity(param);// build entity
     *                 if (null == restTemplate) {
     *                     log.error("fail exec method [restTemplateExchange]. restTemplate is null");
     *                     throw new WebServerException(ConstantCode.SYSTEM_EXCEPTION);
     *                 }
     *                 ResponseEntity<T> response = restTemplate.exchange(url, method, entity, clazz);
     *                 log.info("response:{}", JsonUtils.objToString(response));
     *                 return response.getBody();
     *             } catch (ResourceAccessException e) {
     *                 log.error("fail exec method [restTemplateExchange]. ResourceAccessException.", e);
     *                 continue;
     *             } catch (HttpStatusCodeException e) {
     *                 JsonNode error = JsonUtils.stringToJsonNode(e.getResponseBodyAsString());
     *                 log.error("fail exec method [restTemplateExchange]. error:{}", e.getResponseBodyAsString());
     *                 if (ObjectUtils.isEmpty(error.get("message"))) {
     *                     throw new WebServerException(ConstantCode.REMOTE_SERVER_REQUEST_FAIL);
     *                 }
     *                 String message = error.get("message").asText();
     *                 if (message.contains("code")) {
     *                     JsonNode errorInside = error.get("message").get("error");
     *                     throw new WebServerException(errorInside.get("code").asInt(),
     *                             errorInside.get("message").asText());
     *                 }
     *                 throw new WebServerException(error.get("code").asInt(), message);
     *             }
     *         }
     *         throw new WebServerException(ConstantCode.REMOTE_SERVER_REQUEST_FAIL);
     *     }
     */


    /**
     * build httpEntity
     */

    private HttpEntity buildHttpEntity(Object param) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        String paramStr = null;
        if (param != null) {
            paramStr = JsonUtils.objToString(param);
        }
        HttpEntity requestEntity = new HttpEntity(paramStr, headers);
        return requestEntity;
    }


//    /**
//     * @param addressList
//     * @param urlFormat
//     * @param uri
//     * @return
//     */
//    private String buildRemoteUrl(ArrayList<String> addressList, String urlFormat, String uri) {
//        Collections.shuffle(addressList);//random one
//        Iterator<String> iterator = addressList.iterator();
//        while (iterator.hasNext()) {
//            String url = String.format(urlFormat, iterator.next(), uri).replaceAll(" ", "");
//            iterator.remove();
//            return url;
//        }
//        return null;
//    }


    /**
     * @param response
     * @param dataClass
     * @param <T>
     * @return
     */
    protected <T> T getResultData(BaseResponse response, Class<T> dataClass) {
        if (dataClass == null) {
            log.info("finish exec method [getResultData]. dataClass is null,return null");
            return null;
        }
        if (Objects.isNull(response)) {
            log.info("finish exec method [getResultData]. response is null,return null");
            return null;
        }
        if (Constant.HTTP_SUCCESS_RESPONSE_CODE != response.getCode()) {
            log.info("finish exec method [getResultData]. response code:{} message:{}", response.getCode(), response.getMessage());
            throw new WebServerException(response.getCode(), response.getMessage(), response.getData());
        }

        T t = JsonUtils.objToJavaBean(response.getData(), dataClass);
        log.info("success exec method [getResultData]. data:{}", JsonUtils.objToString(response.getData()));
        return t;
    }


    protected <T> T getResultData(BaseResponse response, TypeReference<T> tTypeReference) {
        if (tTypeReference == null) {
            log.info("finish exec method [getResultData]. tTypeReference is null,return null");
            return null;
        }
        if (Objects.isNull(response)) {
            log.info("finish exec method [getResultData]. response is null,return null");
            return null;
        }
        if (Constant.HTTP_SUCCESS_RESPONSE_CODE != response.getCode()) {
            log.info("finish exec method [getResultData]. response code:{} message:{}", response.getCode(), response.getMessage());
            throw new WebServerException(response.getCode(), response.getMessage(), response.getData());
        }

        T t = JsonUtils.stringToObj(JsonUtils.objToString(response.getData()), tTypeReference);
        log.info("success exec method [getResultData]. data:{}", JsonUtils.objToString(response.getData()));
        return t;
    }


    /**
     * @param response
     */
    protected void checkRestResult(BaseResponse response) {
        if (Objects.isNull(response)) {
            log.info("finish exec method [checkRestResult]. response is null,return null");
            return;
        }
        if (Constant.HTTP_SUCCESS_RESPONSE_CODE != response.getCode()) {
            log.info("finish exec method [checkRestResult]. response code:{} message:{}", response.getCode(), response.getMessage());
            throw new WebServerException(response.getCode(), response.getMessage(), response.getData());
        }
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class RestAddressStatusInfo implements Comparable {
        private String address;// example: 127.0.0.1:8080
        private Long lastFailTime;

        public RestAddressStatusInfo(String address) {
            this.address = address;
        }

        @Override
        public int compareTo(Object o) {
            RestAddressStatusInfo target = (RestAddressStatusInfo) o;
            if (Objects.isNull(this.getLastFailTime()) && Objects.isNull(target.getLastFailTime())) return 0;

            if (Objects.isNull(this.getLastFailTime())) return -1;

            if (Objects.isNull(target.getLastFailTime())) return 1;

            if (target.getLastFailTime() > this.getLastFailTime()) return 1;

            if (target.getLastFailTime() < this.getLastFailTime()) return -1;

            return 0;
        }
    }
}
